Matrix详解

概述

我们在自定义 View 控件时随处可见 Matrix 的身影,主要用于坐标转换映射,我们可以通过 Matrix 矩阵来控制视图的变换。

Matrix 本质上是一个如下图所示的矩阵:

上面每个值都有其对应的操作。

Matrix 提供了如下几个操作:

  • 缩放(Scale)
    对应 MSCALE_XMSCALE_Y

  • 位移(Translate)
    对应 MTRANS_XMTRANS_Y

  • 错切(Skew)
    对应 MSKEW_XMSKEW_Y

  • 旋转(Rotate)
    旋转没有专门的数值来计算,Matrix 会通过计算缩放与错切来处理旋转。

  • 最后三个参数是控制透视的,这三个参数主要在3D效果中运用,通常为(0, 0, 1),不在本篇讨论范围内,暂不过多叙述

示意

假设通知栏高度为20像素,导航栏高度为40像素,那么我们在内容区的(0,0)位置绘制一个点,最终就要转化为在实际坐标系中的(0,60)位置绘制一个点。

原理

矩阵乘法规则还记得吗?

我们在使用 Matrix 处理视图变换时本质上是通过矩阵映射坐标
所以上述的几个操作都是对矩阵的操作,我们新建一个 Matrix 后其矩阵为默认状态,其值如下:

可以看到默认状态下的数据都是初始值,即不做任何变换处理,所有坐标保持原样

缩放

对于单个坐标来说,缩放只要将其坐标值值乘以缩放值即可
假设对某个点宽度缩放 k1 倍,高度缩放 k2 倍,该点坐标为 x0、y0,缩放后坐标为 x、y,那么缩放的公式如下:

用矩阵来描述一下(上面提到了,与缩放相关的两个位置是 MSCALE_XMSCALE_Y,即下图k1,k2的位置)

等号左边的矩阵就是计算后的缩放结果。

Matrix 中用于缩放操作的方法有如下两个:

1
2
void setScale(float sx, float sy);
void setScale(float sx, float sy, float px, float py);

前面两个参数 sxsy,分别是宽和高的缩放比例
第二个重载方法多了两个参数 px、py,这两个参数用来描述缩放的枢轴点

枢轴点是指定转换应保持不变的坐标。
当我们不传这两个参数时,枢轴点默认为左上角的点,缩放都是向下和向右,所以枢轴点可以大概的理解为缩放的锚点,缩放从这个点开始向四周扩散。

通过矩阵来理解一下:

1
2
Matrix matrix = new Matrix()
matrix.setScale(0.5F, 0.5F, 300F, 300F);

缩放 0.5 倍,枢轴点为 300,调用该方法后矩阵变换为:

前面提到过,上图右上角两个150值对应的位置是 MTRANS_XMTRANS_Y,即和平移操作相关的两个位置。因此,实际上我们设置了枢轴点后 Matrix 会做一次位移操作,平移距离就是 s * p.

位移

位移操作是指将坐标(x0,y0)平移一定的距离,我们直接将坐标加上平移的距离即可得到平移后的坐标:

通过矩阵理解(上面提到了,与平移相关的两个位置是 MTRANS_XMTRANS_Y,即下图△x,△y的位置):

用于设置位移操作的方法:

1
void setTranslate(float dx, float dy);
错切

看张图感受一下:

上图是通过下面的代码设置错切的示意图

1
matrix.setSkew(0.3F, 0.3F);

分别设置了水平错切垂直错切的值为 0.3,效果就是上面的样子。
错切公式如下:

通过矩阵理解(上面提到了,与错切相关的两个位置是 MSKEW_XMSKEW_Y,即下图k1,k2的位置):

错切操作方法(和前面缩放一样)

1
2
void setSkew(float kx, float ky);
void setSkew(float kx, float ky, float px, float py);
旋转

旋转公式:

矩阵:

设置旋转的方法:

1
2
void setRotate(float degrees);
void setRotate(float degrees, float px, float py);
Matrix复合变换

复合变换是指矩阵同时实现两种或以上变换,例如在平移的同时改变其大小。

Matrix 的复合变换实际上就是矩阵相乘,原理很简单,但是因为矩阵相乘不符合交换律、且执行顺序对结果会有影响,所以想准确的使用好符合变换需要了解其原理。

上面我们在介绍这几种变换的同时也说了他们对应的方法,可以看到他们都是 set 方法,但 Matrix 中实际上提供了三种操作,分别是:设置(set)、前乘(pre)以及后乘(post)

所以上述介绍的几个 set 方法都有与之对应的 pre 及 post 方法,方法列表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//scale 缩放
boolean preScale(float sx, float sy);
boolean preScale(float sx, float sy, float px, float py);
boolean postScale(float sx, float sy);
boolean postScale(float sx, float sy, float px, float py);

//translate 平移
boolean preTranslate(float dx, float dy);
boolean postTranslate(float dx, float dy);

//skew 错切
boolean preSkew(float kx, float ky);
boolean preSkew(float kx, float ky, float px, float py);
boolean postSkew(float kx, float ky);
boolean postSkew(float kx, float ky, float px, float py);

//rotate 旋转
boolean preRotate(float degrees);
boolean preRotate(float degrees, float px, float py);
boolean postRotate(float degrees);
boolean postRotate(float degrees, float px, float py);

这三种的区别:

  • 设置(set):如果我们不需要考虑复合变换的情况,一般可以直接使用 set 方法,因为 set 方法可能会重置之前的 Matrix 状态,导致之前设置的变换失效。

  • 前乘(pre)

    前乘相当于矩阵右乘:

我们假设当前矩阵 M 为:

用pre方法做一次平移操作

1
matrix.preTranslate(100, 100);

变换过程如下:(可以很明显的看到,当前是右乘

  • 后乘

    后乘相当于矩阵左乘:

    还是用刚才上面那个矩阵,同样对其做一次平移操作,使用post

    1
    matrix.postTranslate(100, 100);

    变换过程如下:(可以看到,是左乘

0%